package ch.ethz.fcl.metrobuzz.render.accessbility.view.global;

import java.io.File;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.lwjgl.opengl.GL11;

import ch.ethz.fcl.metrobuzz.MBContainer;
import ch.ethz.fcl.metrobuzz.algorithm.network.shortest_path.public_transport.OneToAllDijkstraPublic;
import ch.ethz.fcl.metrobuzz.algorithm.network.shortest_path.public_transport.PathPublic;
import ch.ethz.fcl.metrobuzz.algorithm.network.shortest_path.public_transport.Walk;
import ch.ethz.fcl.metrobuzz.applications.AccessibilityApp;
import ch.ethz.fcl.metrobuzz.data.MBData;
import ch.ethz.fcl.metrobuzz.data.accessibility.activity.PointOfInterest;
import ch.ethz.fcl.metrobuzz.data.transit.Stop;
import ch.ethz.fcl.metrobuzz.render.techniques.colors.MBColorMap;
import ch.ethz.fcl.metrobuzz.render.techniques.projection.Projection;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Point2D;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Polygon;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Rectangle;
import ch.ethz.fcl.metrobuzz.spatialindex.object3d.Point3D;
import ch.ethz.fcl.metrobuzz.tools.MBTools;

/**
 * Presents accessibility levels over the whole city
 * 
 * @author Zeng Wei
 * @email zeng@arch.ethz.ch
 * 
 */
public class AccessibilityMapView {
	private MBContainer container;
	private MBData data;

	private boolean initialized = false;

	private float minX, minY, maxX, maxY;

	private AccessibilityMapCell[][] cells;

	private float timeLimit = 60 * 60;
	private int resolution = 50;// 50 meterss

	private MBColorMap colorMap;

	private final String POI_VALUE_FOLDER = "res/Singapore/accessibility/";

	private String accessibilityFile = "Accessibility";
	private String childCareFile = "child_care_centres";

	// private String libraries = "libraries";
	// private String residentsFile = "residents";

	private String workFile = "work";

	public AccessibilityMapView(MBContainer container) {
		this.container = container;
		this.data = this.container.getData();
	}

	private void init() {
		long time = System.currentTimeMillis();

		initAccessibilityCells();
		generateAccessibilityLevels();

		float maxValue = 0;
		for (int i = 0; i < cells.length; i++) {
			for (int j = 0; j < cells[0].length; j++) {
				maxValue = Math.max(maxValue, cells[i][j].getAcceValue());
			}
		}

		// colorMap = new MBColorMap("res/textures/color2_power_of_2.png",

		colorMap = new MBColorMap(null, "Accessibility Value", new String[] {
				"0", MBTools.formatNum("#.#", maxValue) }, new float[] { 0,
				maxValue });

		System.out.println((System.currentTimeMillis() - time)
				+ " ms to init accessibility map");
	}

	private void initAccessibilityCells() {
		if (new File(POI_VALUE_FOLDER + accessibilityFile).exists()) {
			cells = AccessibilityReaderWriter.readAcceCells(POI_VALUE_FOLDER
					+ accessibilityFile);
		} else {
			generateAccessibilityCells();
		}

		for (int i = 0; i < cells.length; i++) {
			for (int j = 0; j < cells[0].length; j++) {
				if (!cells[i][j].isAreaInSg())
					continue;

				// generate nearby stops
				for (Stop stop : data.getPublicTransit().getStops()) {
					float walkCost = Walk.walkCost(data, stop.toPoint(),
							cells[i][j].getCenter());
					// within 10 minutes walk
					if (walkCost <= Walk.MAXIMUM_WALK_LIMIT)
						cells[i][j].addNearbyStop(stop);
				}

				for (Stop s : cells[i][j].getNearbyStops())
					cells[i][j].addStopWalkCost(
							s,
							Walk.walkCost(data, s.toPoint(),
									cells[i][j].getCenter()));
			}
		}
	}

	private void generateAccessibilityCells() {
		minX = data.getScene().getBound()
				.normalizeX(data.getScene().getBound().getMinX());
		maxX = data.getScene().getBound()
				.normalizeX(data.getScene().getBound().getMaxX());
		minY = data.getScene().getBound()
				.normalizeY(data.getScene().getBound().getMinY());
		maxY = data.getScene().getBound()
				.normalizeY(data.getScene().getBound().getMaxY());

		// 50 meters length on the screen
		float scaleLength = data.getScene().getBound()
				.getScreenDist(resolution);

		int xRange = (int) ((maxX - minX) / scaleLength) + 10;
		int yRange = (int) ((maxY - minY) / scaleLength) + 4;

		cells = new AccessibilityMapCell[xRange][yRange];

		for (int i = 0; i < xRange; i++) {
			float curX = minX - 2 * scaleLength + i * scaleLength;
			for (int j = 0; j < yRange; j++) {
				float curY = minY - 2 * scaleLength + j * scaleLength;

				Rectangle area = new Rectangle(curX, curY, curX + scaleLength,
						curY + scaleLength);

				// check if area is within Singapore
				boolean in = false;
				for (Polygon zone : container.getData().getSGInfo()
						.getSGZones().getSingaporeZone()) {
					if (zone.contains(curX, curY)) {
						in = true;
						break;
					}

					if (zone.contains(curX + scaleLength, curY)) {
						in = true;
						break;
					}

					if (zone.contains(curX + scaleLength, curY + scaleLength)) {
						in = true;
						break;
					}

					if (zone.contains(curX, curY + scaleLength)) {
						in = true;
						break;
					}
				}

				cells[i][j] = new AccessibilityMapCell(area);
				cells[i][j].setAreaInSg(in);

			}
		}

		AccessibilityReaderWriter.writeAcceCells(POI_VALUE_FOLDER
				+ accessibilityFile, cells);
	}

	private void generateAccessibilityLevels() {
		// if (new File(POI_VALUE_FOLDER + childCareFile).exists()) {
		// AccessibilityReaderWriter.readAcceValues(POI_VALUE_FOLDER
		// + childCareFile, cells);
		// } else {
		// calAccessibilityLevels(POI_VALUE_FOLDER + childCareFile, data
		// .getActivity().getChildCare());
		// }

		// if (new File(POI_VALUE_FOLDER + residentsFile).exists()) {
		// AccessibilityReaderWriter.readAcceValues(POI_VALUE_FOLDER
		// + residentsFile, cells);
		// } else {
		// calAccessibilityLevels(POI_VALUE_FOLDER + residentsFile, data
		// .getActivity().getResidents());
		// }

		// if (new File(POI_VALUE_FOLDER + libraries).exists()) {
		// AccessibilityReaderWriter.readAcceValues(POI_VALUE_FOLDER
		// + libraries, cells);
		// } else {
		// calAccessibilityLevels(POI_VALUE_FOLDER + libraries, data
		// .getActivity().getLibraries());
		// }

		if (new File(POI_VALUE_FOLDER + workFile).exists()) {
			AccessibilityReaderWriter.readAcceValues(POI_VALUE_FOLDER
					+ workFile, cells);
		} else {
			calAccessibilityLevels(POI_VALUE_FOLDER + workFile, data
					.getActivity().getWork());
		}
	}

	private void calAccessibilityLevels(String fileName,
			List<PointOfInterest> poiList) {
		OneToAllDijkstraPublic spPublic = new OneToAllDijkstraPublic(data);

		for (int i = 0; i < cells.length; i++) {
			for (int j = 0; j < cells[0].length; j++) {
				AccessibilityMapCell cell = cells[i][j];

				if (!cell.isAreaInSg())
					continue;

				Map<PointOfInterest, Float> cellToPoiMap = new HashMap<PointOfInterest, Float>();
				for (Stop s : cell.getNearbyStops()) {
					float cellToStopCost = cell.getStopWalkCost(s);

					LinkedHashMap<Stop, PathPublic> publicPaths = spPublic
							.getShortestPath(s, timeLimit);

					for (PointOfInterest poi : poiList) {
						for (Stop poiNearbyStop : poi.getNearbyStops()) {
							PathPublic path = publicPaths.get(poiNearbyStop);

							if (path == null)
								continue;

							float cost = cellToStopCost + path.getCost()
									+ poi.getStopWalkCost(poiNearbyStop);
							if (cost <= timeLimit) {
								if (cellToPoiMap.containsKey(poi)) {
									float existCost = cellToPoiMap.get(poi);
									if (existCost > cost)
										cellToPoiMap.put(poi, cost);
								} else
									cellToPoiMap.put(poi, cost);
							}
						}
					}
				}

				for (PointOfInterest poi : poiList) {
					float walkCost = Walk.walkCost(data, poi.toPoint(),
							cells[i][j].getCenter());
					if (walkCost <= Walk.MAXIMUM_WALK_LIMIT) {
						if (cellToPoiMap.containsKey(poi)) {
							float existCost = cellToPoiMap.get(poi);
							if (existCost > walkCost)
								cellToPoiMap.put(poi, walkCost);
						} else
							cellToPoiMap.put(poi, walkCost);
					}

				}

				float accessibility = 0;
				for (PointOfInterest poi : cellToPoiMap.keySet()) {
					float cost = cellToPoiMap.get(poi);
					accessibility += 1.0f / Math.log(cost);
				}

				cell.setAcceValue(accessibility);

				System.out.println("Accessibility value calculated for "
						+ (i * cells.length + j) + " cell among total "
						+ (cells.length * cells.length));
			}
		}

		AccessibilityReaderWriter.writeIntoFile(fileName, cells,
				"#50m x 50m aeas");
	}

	// This is more efficient to calculate accessibility, but costs too much
	// memory
	void oldCalAccessibilityLevels(String fileName,
			List<PointOfInterest> poiList) {
		Map<Stop, PoiCostMap> stopToPoiMap = new HashMap<Stop, PoiCostMap>();
		OneToAllDijkstraPublic spPublic = new OneToAllDijkstraPublic(data);

		for (Stop s : data.getPublicTransit().getStops()) {
			LinkedHashMap<Stop, PathPublic> publicPaths = spPublic
					.getShortestPath(s, timeLimit);

			for (PointOfInterest poi : poiList) {
				for (Stop nearbystop : poi.getNearbyStops()) {
					PathPublic p = publicPaths.get(nearbystop);

					if (p == null)
						continue;

					float cost = p.getCost() + poi.getStopWalkCost(nearbystop);
					if (cost <= timeLimit) {
						PoiCostMap poiCost = stopToPoiMap.get(s);
						if (poiCost == null) {
							poiCost = new PoiCostMap(s);
							stopToPoiMap.put(s, poiCost);
						}

						poiCost.addPOICost(poi, cost);
					}
				}
			}
		}

		for (int i = 0; i < cells.length; i++) {
			for (int j = 0; j < cells[0].length; j++) {
				AccessibilityMapCell cell = cells[i][j];

				if (!cell.isAreaInSg())
					continue;
				for (Stop s : cell.getNearbyStops()) {
					PoiCostMap poiCostMap = stopToPoiMap.get(s);

					if (poiCostMap == null)
						continue;

					Map<PointOfInterest, Float> poiMap = poiCostMap
							.getPoiCostMap();

					float walkCost = cell.getStopWalkCost(s);
					for (PointOfInterest poi : poiMap.keySet()) {
						float cost = poiMap.get(poi) + walkCost;
						if (cost <= timeLimit)
							cell.addAccessiblePoi(poi, cost);
					}
				}
			}
		}

		for (int i = 0; i < cells.length; i++) {
			for (int j = 0; j < cells[0].length; j++) {
				Map<PointOfInterest, Float> accePoiMap = cells[i][j]
						.getAccessiblePoiMap();

				float accessibility = 0;
				for (PointOfInterest poi : accePoiMap.keySet()) {
					Float cost = accePoiMap.get(poi);
					accessibility += 1.0f / Math.log(cost);
				}

				cells[i][j].setAcceValue(accessibility);
			}
		}
		AccessibilityReaderWriter.writeIntoFile(fileName, cells,
				"#200M x 200M aeas");
	}

	public void render() {
		if (!initialized) {
			init();
			initialized = true;
		}

		setStenci();
		renderAxis();

		GL11.glDisable(GL11.GL_STENCIL_TEST);

		data.getRoadNetwork().render();
		
		GL11.glBegin(GL11.GL_LINES);
		GL11.glVertex2f(-1f, -1f);
		GL11.glVertex2f(1f, -1f);
		

		GL11.glVertex2f(-1f, -1f);
		GL11.glVertex2f(-1f, 1f);
		GL11.glEnd();

		renderColorMap();
	}

	private void renderAxis() {
		GL11.glLineWidth(2.0f);

		for (int i = 0; i < cells.length; i++)
			for (int j = 0; j < cells[0].length; j++) {
				cells[i][j].render(colorMap);
			}
	}

	private void renderColorMap() {
		Projection proj = container.getViewer().getProjection();

		int width = 250, height = 20;
		int screenWidth = AccessibilityApp.CANVAS_WIDTH;

		Point3D min = proj.getWorldCoord(new Point2D(screenWidth - width - 40,
				20), 0);

		Point3D max = proj.getWorldCoord(new Point2D(screenWidth - 40,
				20 + height), 0);

		colorMap.render(container, min.getX(), min.getY(), max.getX(),
				max.getY(), 90);
	}

	protected void setStenci() {
		GL11.glClear(GL11.GL_STENCIL_BUFFER_BIT);
		GL11.glEnable(GL11.GL_STENCIL_TEST);

		GL11.glColorMask(false, false, false, false);

		GL11.glStencilFunc(GL11.GL_ALWAYS, 0, 0);
		GL11.glStencilOp(GL11.GL_REPLACE, GL11.GL_REPLACE, GL11.GL_REPLACE);

		// set not paintable for whole screen
		GL11.glBegin(GL11.GL_POLYGON);
		GL11.glVertex3f(-1, -1, 0);
		GL11.glVertex3f(-1, 1, 0);
		GL11.glVertex3f(1, 1, 0);
		GL11.glVertex3f(1, -1, 0);
		GL11.glEnd();

		GL11.glStencilFunc(GL11.GL_ALWAYS, 1, 1);
		GL11.glStencilOp(GL11.GL_REPLACE, GL11.GL_REPLACE, GL11.GL_REPLACE);

		// set paintable for the area of Singapore
		for (Polygon zone : container.getData().getSGInfo().getSGZones()
				.getSingaporeZone()) {
			zone.renderArea();
		}

		// render setting
		GL11.glColorMask(true, true, true, true);
		GL11.glStencilFunc(GL11.GL_EQUAL, 1, 1);
		GL11.glStencilOp(GL11.GL_KEEP, GL11.GL_KEEP, GL11.GL_KEEP);
	}

	class PoiCostMap {
		Stop s;

		Map<PointOfInterest, Float> poiCostMap;

		public PoiCostMap(Stop stop) {
			this.s = stop;
			poiCostMap = new HashMap<PointOfInterest, Float>();
		}

		public void addPOICost(PointOfInterest poi, float cost) {
			Float poiCost = poiCostMap.get(poi);

			if (poiCost == null)
				poiCostMap.put(poi, cost);
			else {
				float minCost = Math.min(cost, poiCost);
				poiCostMap.put(poi, minCost);
			}
		}

		public Map<PointOfInterest, Float> getPoiCostMap() {
			return poiCostMap;
		}
	}
}
